Git Advanced Workshop

Tibor Stanko

2023-10-18

1 Úvod

O mne

  • Tibor Stanko, 32 rokov
  • od 2020 dátový inžinier v Zurich Insurance, Bratislava 🇸🇰
  • predtým 6 rokov v akademickej sfére vo 🇫🇷 (PhD, postdoc)
  • rád automatizujem nudné úlohy s pomocou Pythonu 🐍
  • nie som Git guru, no Git používam denne už viac ako 8 rokov
  • moje voľnočasové aktivity: 👨‍👩‍👧‍👦🚲⛰️🎸🎹🍺

Obsah tohto workshopu

2 Vnútro Gitu

Čo sa skrýva vo vnútri Gitu?

Čo je to Git?

  • systém riadenia verzií
  • angl. version control system (VCS) alebo source control management (SCM)
  • zaznamenáva históriu vývoja projektu
  • užitočný pre tímy aj pre jednotlivcov
  • nie je len o kóde, dovoľuje ukladať ľubovoľné súbory (aj netextové)

Ale… Čo je skutočne Git?

  • Git je obsahovo adresovateľný systém súborov
  • To znamená, že adresa súboru (kľúč) je definovaná pomocou jeho obsahu
  • V jadre systému Git sa nachádza jednoduché úložisko údajov, ku ktorým sa dá pristupovať pomocou kľúčov
  • Kľúč = SHA-1 hash , napr. 655a20f99af32926cbf6d8fab092506ddd70e49c

Čo Git ukladá?

Ide najmä o:

  • objekty (objects)
  • referencie (references alebo refs)

Objekty

  1. blob
    • len obsah, žiadne metadáta (cesta, meno)
  2. tree = strom
    • strom obsahuje bloby alebo ďalšie stromy
  3. commit = záznam
    • obsahuje ukazovatele na strom a iný commit (rodič)
  4. tag
    • definuje alternatívne meno pre iný objekt, ktoré môže byť použité na interakciu s objektom namiesto hashu

Každý objekt je identifikovateľný s pomocou svojho SHA-1 hashu.

Referencie = ukazovatele na objekty

  • vetva nie je sled commitov, ale len ukazovateľ (pointer) na určitý commit
cat .git/refs/heads/main
# 7c66409021358486e63d2d40c9b07e2c35e8124d

cat .git/refs/remotes/origin/dev
# c29dc332ac3eebebffc5726e16d0e91df170103f

cat .git/refs/tags/v2.6.3
# d49de0ec577052db3e47e2baf5aff0be738637ac

Tip: v powershelli môžeš namiesto príkazu cat použiť gc (alias pre Get-Content)

Typy príkazov

Porcelain:

  • high-level príkazy, ktoré používa bežný smrteľník
  • commit, log, merge, pull, push, status, …

Plumbing:

  • “core git”
  • low-level príkazy, používané interne Gitom (príp. powerusermi)
  • cat-file, commit-tree, hash-object, ls-files, merge-base, rev-parse, …

Demo

  • powershell
  • .git tree

cd ~
git init test
# Initialized empty Git repository in 
# C:/Users/tibor.stanko/test/.git/
cd test
.git
├── HEAD    ref: refs/heads/main
│
├── objects
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
└── refs
    ├── heads
    |
    └── tags

echo "version 1" > test.txt
git status
# On branch main
# No commits yet
#
# Untracked files:
#         test.txt
.git
├── HEAD    ref: refs/heads/main
│
├── objects
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
└── refs
    ├── heads
    |
    └── tags

git add test.txt
git status
# On branch main
# No commits yet
#
# Changes to be committed:
#         test.txt
git ls-files --stage
# 100644 594dc0e39bc4468ee19c
#        67e65d37b97eb963b68b 0 test.txt
.git
├── HEAD    ref: refs/heads/main
├── index
├── objects
│   └── 59 blob [test.txt] 'version 1'
│       └── 4dc0e39bc4468ee19c67e65d37b97eb963b68b
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
└── refs
    ├── heads
    |
    └── tags

git commit -m "First"
# [main (root-commit) c771cdc] First
#  1 file changed, 0 insertions(+),
#   ... 0 deletions(-)
#  create mode 100644 test.txt
git status
# On branch main
# nothing to commit, working tree clean
.git
├── HEAD    ref: refs/heads/main
├── index
├── objects
│   ├── 59 blob [test.txt] 'version 1'
│   │   └── 4dc0e39bc4468ee19c67e65d37b97eb963b68b
│   ├── 67 tree [blob 594d]
│   │   └── 4d4d31b97233152f3be1825cc9e765fa2b2859
│   └── f8 commit [tree 674d] "First"
│       └── 0a04ee3dfbeb5eb666ade615abc617c1ea20e3
│
│
│
│
│
│
│
│
│
│
│
│
└── refs
    ├── heads
    |   └── main f80a...
    └── tags

mkdir bak
mi test.txt bak
echo "version 2" > test.txt
git add .
git commit -m "Second"
# [main 4ba094f] Second
#  2 files changed, 0 insertions(+), 
#   ... 0 deletions(-)
#  create mode 100644 bak/test.txt
.git
├── HEAD    ref: refs/heads/main
├── index
├── objects
│   ├── 59 blob [test.txt] 'version 1'
│   │   └── 4dc0e39bc4468ee19c67e65d37b97eb963b68b
│   ├── 67 tree [blob 594d]
│   │   └── 4d4d31b97233152f3be1825cc9e765fa2b2859
│   ├── f8 commit [tree 674d] "First"
│   │   └── 0a04ee3dfbeb5eb666ade615abc617c1ea20e3
│   ├── f0 blob [test.txt] 'version 2'
│   │   └── d983103c610431663d84b3012d1b172f2f52ea
│   ├── 37 tree [tree 674d, blob f0d9]
│   │   └── 87931e43c8baf51f3ffafc44f6394651a505ca
│   └── 53 commit [tree 3787, par. f80a] "Second"
│       └── 9f7e662b0fa2ceb0df1dc9332179b06e5cdaec
│
│
│
│
│
│
└── refs
    ├── heads
    |   └── main 539f...
    └── tags

echo "new" > new.txt
git add new.txt
git commit -m "Third"
# [main 62e37a9] Third
#  1 file changed, 0 insertions(+), 
#   ... 0 deletions(-)
#  create mode 100644 new.txt
.git
├── HEAD    ref: refs/heads/main
├── index
├── objects
│   ├── 59 blob [test.txt] 'version 1'
│   │   └── 4dc0e39bc4468ee19c67e65d37b97eb963b68b
│   ├── 67 tree [blob 594d]
│   │   └── 4d4d31b97233152f3be1825cc9e765fa2b2859
│   ├── f8 commit [tree 674d] "First"
│   │   └── 0a04ee3dfbeb5eb666ade615abc617c1ea20e3
│   ├── f0 blob [test.txt] 'version 2'
│   │   └── d983103c610431663d84b3012d1b172f2f52ea
│   ├── 37 tree [tree 674d, blob f0d9]
│   │   └── 87931e43c8baf51f3ffafc44f6394651a505ca
│   ├── 53 commit [tree 3787, par. f80a] "Second"
│   │   └── 9f7e662b0fa2ceb0df1dc9332179b06e5cdaec
│   ├── dc blob [new.txt] 'new'
│   │   └── 334bff12fb7d7404c79935fa3ba535c3bb28d0
│   ├── b0 tree [tree 674d, blob f0d9, blob dc33]
│   │   └── ea95a512bad604278bcc96e8b8e726b462e010
│   └── 62 commit [tree b0ea, par. 539f] "Third"
│       └── e37a96f8f09d0421644817dea320108ceac481
└── refs
    ├── heads
    |   └── main 62e3...
    └── tags

 

.git pre reálny projekt

pybrickz/.git
│   COMMIT_EDITMSG  posledný popis záznamu upravený v lokálnom repozitári
│   config          lokálna konfigurácia, aplikuje sa iba na tento repozitár
│   description     don't worry about it
│   FETCH_HEAD      pamätá si, čo bolo naposledy stiahnuté zo vzdialeného repozitára
│   HEAD            ukazovateľ na aktuálnu vetvu/commit
│   index           binárny zoznam ciest a SHA-1 hashov, obsah zobraz cez `git ls-files --stage`
│   ORIG_HEAD       predchádzajúci stav HEAD, nastavený príkazmi s potenciálne nebezpečným správaním
│   packed-refs     zabalené referencie (heads, tags)
├───hooks […]
├───info […]
├───logs […]
├───objects […]
└───refs […]

.git/objects

pybrickz/.git
│   […]
├───hooks […]
├───info […]
├───logs […]
├───objects  objekty - bloby, stromy (trees), commity
│   ├───00
│   │       57f7cf16175d94fa850ad30918dffcd4cd850c
│   ├───01
│   │       4daec1e8a05a71852209c4caf9750bfe4717b1
...
│   ├───fe
│   │       1c754ef352dece245b5f7a0d7047b048d7b1d9
│   │       8a73f88812537678fde89e91c19c87623ff47c
│   ├───ff
│   │       7e837bf1dc59b8835767fdcf789e308528498a
│   ├───info […]
│   └───pack […]
└───refs […]

.git/refs

pybrickz/.git
│   […]
├───hooks […]
├───info […]
├───logs […]
├───objects […]
└───refs     referencie - vetvy, vzdialené vetvy, tagy
    ├───heads […]
    ├───remotes […]
    └───tags […]

.git/refs/heads

pybrickz/.git
│   […]
├───hooks […]
├───info […]
├───logs […]
├───objects […]
└───refs
    ├───heads
    │       dev
    │       main
    │       staging
    │       ├───bugfix
    │       │       bugfix-1    vetva vytvorená cez `git branch bugfix/bugfix-1`
    │       │       bugfix-2    vetva vytvorená cez `git branch bugfix/bugfix-2`
    │       └───feature
    │               feature-A   vetva vytvorená cez `git branch feature/feature-A`
    │               feature-B   vetva vytvorená cez `git branch feature/feature-B`
    ├───remotes […]
    └───tags […]

.git/refs/remotes

pybrickz/.git
│   […]
├───hooks […]
├───info […]
├───logs […]
├───objects […]
└───refs
    ├───heads […]
    ├───remotes
    │   ├───gh
    │   │       main
    │   └───origin
    │       │   dev
    │       │   HEAD
    │       │   main
    │       ├───bugfix
    │       │       bugfix-2
    │       └───feature
    │               my-awesome-feature-A
    └───tags […]

.git/refs/tags

pybrickz/.git
│   […]
├───hooks […]
├───info […]
├───logs […]
├───objects […]
└───refs
    ├───heads […]
    ├───remotes […]
    └───tags
            v1.0.0
            v1.0.1
            ...
            v2.6.4
            v2.7.0

.git/HEAD

  • HEAD je ukazovateľ na aktuálnu vetvu alebo commit
  • DETACHED HEAD je situácia keď HEAD ukazuje na commit ktorý nie je hlavou vetvy
cat .git/HEAD
# ref: refs/heads/main
git checkout 7c66409
git status
# HEAD detached at 7c66409
# nothing to commit, working tree clean
cat .git/HEAD
# 7c66409021358486e63d2d40c9b07e2c35e8124d

Úlohy (1)

  1. Naklonuj si testovací repozitár:
git clone https://github.com/bbrrck/hello.git 
  1. Z priečinku .git nájdi súbor s hashom na ktorý ukazuje vetva slovak.
  2. Porovnaj hash s výstupom z príkazu git rev-parse slovak.
  3. Viacnásobným použitím príkazu git cat-file -p zisti aký obsah sa nacháda v súbore hello.py na vetve slovak.
    • Hint: ako argument za -p použi hash z predošlého kroku.

3 Merge vs. Rebase

Zlučovanie zmien

V Gite existujú dva hlavné spôsoby, ako integrovať zmeny z jednej vetvy do druhej: merge a rebase.

Zlučovanie zmien

Zlúčenie cez merge

  • najjednoduchší spôsob zlučovania vetiev
  • trojcestné zlúčenie medzi dvoma vetvami (napr. main a feature) a ich najnovším spoločným predkom
  • vytvorí nový commit

Zlúčenie cez merge

Zlúčenie cez rebase

  • rebase vetvy feature na vetvu main znamená presunutie začiatku vetvy feature na koniec vetvy main
  • znamená to, že commity z feature budú znova vytvorené na vetve main
  • hlavná výhoda: čistejšia, lineárnejšia história projektu a menej “vidličiek”

Zlúčenie cez rebase

Kedy nepoužívať rebase?

  • nikdy nepoužívajte git rebase na verejných alebo kolaboratívnych vetvách (najmä main)
  • v opačnom prípade môže dôjsť ku zmene alebo dokonca zmazaniu časti histórie

Kedy nepoužívať rebase?

Demo: merge

git clone https://github.com/bbrrck/hello.git hello-merge; cd hello-merge
git merge origin/french
# Auto-merging hello.py
git merge origin/slovak
# CONFLICT (content): Merge conflict in hello.py
# ... vyrieš konflikt ...
git add .
git commit
# [main cef4a72] Merge remote-tracking branch 'origin/slovak'

Demo: rebase (french)

git clone https://github.com/bbrrck/hello.git hello-rebase; cd hello-rebase
git checkout french
git rebase main
# Successfully rebased and updated refs/heads/french.
git checkout main
git merge french
# Updating 0297280..5f6f019
# Fast-forward
#  hello.py | 11 ++++++++++-
#  1 file changed, 10 insertions(+), 1 deletion(-)

Demo: rebase (slovak) - konflikt

git checkout slovak
git rebase -i main
# ... označ prostredný commit ako `fixup` ...
# CONFLICT (content): Merge conflict in hello.py
# ... vyrieš konflikt ...
git add .
git rebase --continue
# Successfully rebased and updated refs/heads/slovak.
git checkout main
git merge slovak # Fast-forward

git log --oneline --graph --all

Merge:

*   cef4a72 (main) Merge branch 'slovak'
|\
| * 163a9c3 (slovak) Add docstring for slovak
| * bd67d8d Fix slovak
| * 75fcf88 Add slovak
* |   bc3f86b Merge branch 'french'
|\ \
| * | a31caf9 (french) Add docstring for french
| * | 6d348f3 Add french
| |/
* / 0297280 Add docstring for default
|/
* 4b4a8ad Add hello.py
* 60d4d94 Initial commit

Rebase:

* ab2fda1 (main, slovak) Add docstring for slovak
* 806b97a Add slovak
* 5f6f019 (french) Add docstring for french
* ea40a3b Add french
* 0297280 Add docstring for default
| * 163a9c3 Add docstring for slovak
| * bd67d8d Fix slovak
| * 75fcf88 Add slovak
|/
| * a31caf9 Add docstring for french
| * 6d348f3 Add french
|/
* 4b4a8ad Add hello.py
* 60d4d94 Initial commit

git log main --oneline

Merge (10):

cef4a72 (main) Merge branch 'slovak'
bc3f86b Merge branch 'french'
0297280 Add docstring for default
163a9c3 (slovak) Add docstring for slovak
bd67d8d Fix slovak
75fcf88 Add slovak
a31caf9 (french) Add docstring for french
6d348f3 Add french
4b4a8ad Add hello.py
60d4d94 Initial commit

Rebase (7):

ab2fda1 (main, slovak) Add docstring for slovak
806b97a Add slovak
5f6f019 (french) Add docstring for french
ea40a3b Add french
0297280 Add docstring for default
4b4a8ad Add hello.py
60d4d94 Initial commit

Úlohy (2)

  1. Naklonuj si k sebe dve kópie testovacieho repozitára:
git clone https://github.com/bbrrck/zoo.git zoo-merge
git clone https://github.com/bbrrck/zoo.git zoo-rebase
  1. V repozitári zoo-merge:
    1. Zlúč cez príkaz git merge vetvy origin/krokodil a origin/gorila do lokálnej vetvy main.
    2. V oboch prípadoch vyrieš vzniknuté konflikty.

(pokračovanie na ďalšom slajde)

Úlohy (2)

  1. V repozitári zoo-rebase:
    1. Prepni sa na vetvu gorila a zlúč na ňu cez príkaz git rebase vetvu main. Vyrieš vzniknuté konflikty.
    2. Prepni sa na vetvu main a zlúč na ňu cez príkaz git merge vetvu gorila.
    3. Prepni sa na vetvu krokodil a zlúč na ňu cez príkaz git rebase -i vetvu main. Prostredný commit označ ako fixup. Vyrieš vzniknuté konflikty.
    4. Prepni sa na vetvu main a zlúč na ňu cez príkaz git merge vetvu krokodil.
  2. S pomocou príkazu git log porovnaj stav oboch repozitárov.
  3. Čo by sa vo výsledku zmenilo, ak by si vynechal(a) krok 3b?

4 Časté otázky a problémy

git revert

git revert HEAD   # odstráň zmeny vykonané v poslednom commite
git revert HEAD~1 # odstráň zmeny vykonané v predposlednom commite
git revert d49de0 # odstráň zmeny vykonané v commite s hashom d49de0

Príkaz git revert vytvorí novú verziu, a nemení históriu repozitára.

git reset

# vráť repozitár do stavu *po* commite s hashom d49de0
git reset --hard d49de0

Príkaz git reset mení históriu repozitára a môže spôsobiť stratu súborov.

reset vs revert vs checkout

Príkaz Kontext Použitie
git reset Commit Zahoď commity v súkromnej vetve alebo zahoď necommitnuté zmeny
git reset Súbor Odstráň súbor z prípravnej zóny (z indexu)
git checkout Commit Presun medzi vetvami alebo prezeranie starých verzií
git checkout Súbor Zahoď zmeny v pracovnom adresári
git revert Commit Vráť commity vo verejnej vetve
git revert Súbor (N/A)

Ako vrátim späť lokálne zmeny?

git commit -m "Something terribly misguided"
git reset HEAD~1
# ... uprav súbory podľa potreby ...
git add .
git commit -c ORIG_HEAD # otvor predošlý popis v commit editore
git commit -C ORIG_HEAD # použi predošlý popis bez otvorenia commit editoru

Ako vrátim späť zmeny ktoré už boli pushnuté?

git commit -m "Something terribly misguided"
git push origin main
git reset HEAD~1
# ... uprav súbory podľa potreby ...
git add .
git commit -C ORIG_HEAD # použi predošlý popis bez otvorenia commit editoru
git push origin main --force-with-lease # nepouživaj '--force'!

Ako vrátim späť časť commitu?

git revert -n $bad_commit  # vráť commit späť, ale neukladaj zmeny
git reset HEAD .           # zruš pridanie zmien
git add --patch .          # pridaj požadované zmeny
git commit                 # vytvor commit z týchto zmien
git checkout -- .          # odstráň ostatné zmeny

Pozn.: Zmeny, ktoré pridávame pomocou príkazu git add --patch, sú zmeny, ktoré chceme vrátiť späť, nie zmeny, ktoré chceme ponechať.

Ako premenujem lokálnu vetvu?

# premenuj aktuálnu vetvu na 'newname'
git branch -m newname

# premenuj vetvu 'oldname' na 'newname'
git branch -m oldname newname

Ako vymažem lokálnu aj vzdialenú vetvu?

git push -d <remote_name> <branch_name>
git branch -d <branch_name>
git fetch --all --prune # vymaž referencie na vymazané vzdialené vetvy

Ako presuniem posledných n commitov na novú vetvu?

Presunutie posledných 3 commitov z main na novú vetvu feature:

git checkout main           # prepni sa na main
git branch feature          # vytvor novú vetvu feature ktorá ukazuje na rovnaký
                            # commit ako main, ale neprepinaj sa na ňu
git reset --hard HEAD~3     # vymaž posledné 3 commity
git checkout feature        # prepni sa na vetvu feature

Ako odstránim súbory z prípravnej zóny?

git rm --cached <file>

Ako zmením popis v už vytvorenom commite?

git commit --amend -m "New commit message"

Ak už bol starý commit pushnutý na remote, po použití git commit --amend je potrebné pushnúť cez git push --force alebo --force-with-lease.

Ako pridám súbory do už vytvoreného commitu?

git add zabudnuty_subor
git commit --amend --no-edit # znovu použi predošlý commit message

Ak už bol starý commit pushnutý na remote, po použití git commit --amend je potrebné pushnúť cez git push --force alebo --force-with-lease.

Ako môžem resetovať alebo vrátiť súbor na konkrétnu verziu?

git checkout c5f567 -- file1/to/restore file2/to/restore

5 CI/CD s pomocou Gitu

Čo je to CI/CD?

  • CI = continuous integration
  • CD = continuous delivery

6 SSH autentifikácia

Vytvorenie SSH kľúča

ssh-keygen # use default settings
ssh-keygen -t rsa -C "name@email.com" # compatible with most git providers
ssh-keygen -t ed25519 -C "name@email.com" # compatible with GitHub

Skopírovanie SSH kľúča do schránky

Powershell

Get-Content ~\.ssh\id_ed25519.pub | Set-Clipboard

 

Command Prompt

clip < ~\.ssh\id_ed25519.pub

Odkazy

sk

Odkazy

en

Git slovník

en sk
branch vetva
clone naklonovanie repozitára
commit záznam
commit message popis záznamu
conflict konflikt medzi verziami
conflict resolution riešenie konfliktov
diff rozdiel medzi verziami
merge zlúčenie vetiev
en sk
pull stiahnutie vzdialených zmien
push odoslanie lokálnych zmien
repository repozitár, úložisko
remote vzdialený repozitár
snapshot snímka
staging area prípravná oblasť (tiež index)
status stav repozitára
version verzia

Git Cheatsheet

# Setup
git config --global user.name "[first last]"
git config --global user.email "[valid-email]"
git init
git clone [url]
# Stage & Snapshot
git status
git add [file]
git reset [file]
git diff
git diff --staged
git commit -m "[descriptive message]"
# Branch & Merge
git branch
git branch [branch-name]
git checkout
git merge [branch]
git log
# Share & Update
git remote add [alias] [url]
git fetch [alias]
git merge [alias]/[branch]
git push [alias]/[branch]
git pull

Ďakujem za pozornosť! 👋